package com.cwj.jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.profile.StackProfiler;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator;

/**
 * @author chenwujie
 * @date 2021-03-08 15:17
 */
@Measurement(iterations = 10)
@Warmup(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
@Fork(1)
public class AtomicReferenceTest {
    public static void main(String[] args) throws RunnerException {
        Options opts = new OptionsBuilder().include(AtomicReferenceTest.class.getSimpleName())
            .timeout(TimeValue.seconds(10))
            .addProfiler(StackProfiler.class)
            .build();
        new Runner(opts).run();
    }

    @State(Scope.Group)
    public static class MonitorRace{
        private DebitCard ref = new DebitCard("tom", 0);
        public void syncInc(){
            synchronized (AtomicIntegerTest.class){
                ref = new DebitCard(ref.getAccount(), ref.getAmount() + 10);
            }
        }
    }

    @State(Scope.Group)
    public static class AtomicReferenceRace{
        private AtomicReference<DebitCard> ref = new AtomicReference<>(new DebitCard("tom", 0));
        public void casInc(){
            ref.getAndUpdate(e -> new DebitCard(e.getAccount(), e.getAmount() + 10));
        }
    }

    @GroupThreads(10)
    @Group("sync")
    @Benchmark
    public void syncInc(MonitorRace monitorRace){
        monitorRace.syncInc();
    }

    @GroupThreads(10)
    @Group("cas")
    @Benchmark
    public void casInc(AtomicReferenceRace atomicReferenceRace){
        atomicReferenceRace.casInc();
    }

    public static class DebitCard{
        private String account;
        private int amount;

        public DebitCard(String account, int amount) {
            this.account = account;
            this.amount = amount;
        }

        public String getAccount() {
            return account;
        }

        public void setAccount(String account) {
            this.account = account;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }
    }
}
